/* Copyright (C) 2003, Charles Wang.
 * Author:  Charles Wang <charles@linux.net.cn>
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANT; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public LIcense for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 */
#include  <sys/types.h>
#include  <sys/wait.h>
#include  <sys/stat.h>
#include  <sys/mount.h>
#include  <errno.h>
#include  <fcntl.h>
#include  <stdio.h>
#include  <stdlib.h>
#include  <string.h>
#include  <unistd.h>
#include  <newt.h>
#include  <kudzu/kudzu.h>
#include  "mount_guess_fstype.h"
#include  "mblog.h"
#include  "utils.h"
#include  "newtutils.h"

/* /usr/share/hwdata/pcitable is no longer availabe in new hwdata rpm. */
/* #define   USE_PCITABLE */

extern const char     * miimages_cddir;
extern const char     * drvfd[];
extern const unsigned   num_drvfd;

/* check_drvfd return true if the driver floppy with the specified name has
 * been loaded already. */
static int  check_drvfd(const char *drvfdname);
static void search_drvfd_in_all_cdroms(void);
static void search_drvfd_cdrom(const char *device);
static void load_drvfd(const char *device);

int   check_whether_all_drvfd_loaded(void);

const char * loadfloppy_in_cdrom_runner(void) {
    if (!check_whether_all_drvfd_loaded())
	search_drvfd_in_all_cdroms();
    return NULL;
}

int   check_whether_all_drvfd_loaded(void) {
    int  i;
    for (i = 0; i < num_drvfd; ++i)
	if (!check_drvfd(drvfd[i]))  return 0;
    return 1;
}

static int  check_drvfd(const char *drvfdname) {
    struct stat sbuf;
    char  chknamebuf[256];
    snprintf(chknamebuf, sizeof(chknamebuf), "/tmp/drvfd.names/%s", drvfdname);
    return  !stat(chknamebuf, &sbuf);
}

const char * loadfloppy_runner(void) {
    load_drvfd("/dev/fd0");
    return  NULL;
}

static void search_drvfd_in_all_cdroms(void) {
    int            i, num_cd;
#if !defined(NONEWT)
    newtComponent  label, scale;
#endif
    struct device ** cdroms;
    int            markfd;

    newtCenteredWindow(52, 5, "Please wait...");

#if !defined(NONEWT)
    label = newtLabel(1, 1, "");
#endif

    cdroms = probeDevices(CLASS_CDROM, BUS_IDE | BUS_SCSI, PROBE_ALL);
    if (cdroms == NULL) {
        mblog("No CDROMs found!");
        return;
    }

    for (num_cd = 0; cdroms[num_cd] != NULL; ++num_cd)
        ;
#if !defined(NONEWT)
    scale = newtScale(1, 3, 50, num_cd, 0);
    newtScaleSet(scale, num_cd);
    newtRefresh();

    newtLabelSetText(label, "Please wait while search boot CDROM in your machine.");
#else                         /* NONEWT */
    newtCenteredWindow(52, 5, "Please wait while search boot CDROM in your machine.");
#endif

    for (i = 0; i < num_cd; ++i) {
#if !defined(NONEWT)
	newtScaleSet(scale, i);
#else                         /* NONEWT */
        printf("Searching cdrom %s.\n", cdroms[i]->device);
#endif
	search_drvfd_cdrom(cdroms[i]->device);
    }

    /* Mark we have search the cdrom. */
    if ((markfd = open("/tmp/drvfd.cd.searched", O_CREAT | O_WRONLY)) >= 0)
	close(markfd);

#if !defined(NONEWT)
    newtPopWindow();
#else                         /* NONEWT */
    printf("\n");
#endif
    return;
}

static void search_drvfd_cdrom(const char *device) {
    int          i;
    const char * fstype;
    char         devicepath[64], chknamebuf[256];
    struct stat  sbuf;

    snprintf(devicepath, sizeof(devicepath), "/dev/%s", device);
    fstype = do_guess_fstype(devicepath);
    if (!fstype)  return;
    if (mount(devicepath, "/tmp/device.mnt", fstype, MS_RDONLY, NULL)) {
	mblog("Mount(%s, /tmp/device.mnt, %s, MS_RDONLY, NULL) failed: %s\n",
	      devicepath, fstype, strerror(errno));
	return;
    }
    for (i = 0; i < num_drvfd; ++i) {
	if (check_drvfd(drvfd[i]))  continue;
	snprintf(chknamebuf, sizeof(chknamebuf),
		 "/tmp/device.mnt/%s/mb%s", miimages_cddir, drvfd[i]);
	if (stat(chknamebuf, &sbuf))  continue;
	load_drvfd(chknamebuf);
    }
    umount("/tmp/device.mnt");
}

#ifdef USE_PCITABLE
static void merge_pciids(void);
#endif
static void load_drvfd(const char *device) {
    const char *fstype;
    int  namefd;  char namebuf[32];
    ssize_t  rl;  char chknamebuf[256];
    struct stat sbuf, sbmnt;
    int  cpiofd;
    int  p[2];  pid_t  pid;
#ifdef USE_PCITABLE
    int  pcitable_rfd, pcitable_wfd;
#endif
    int  markfd;
    int  r, status;

    if (!strcmp(device, "/dev/fd0"))
	newtWinMessage("Prompt", "OK", "Please insert the driver floppy and press OK.");

    fstype = do_guess_fstype(device);
    if (fstype == NULL) {
	newtWinMessage("Error", "OK",
		       "Not any recognized filesystem found in floppy,"
		       " do you insert any floppy into your floppy drive?");
	goto  err_ret0;
    }
    if (stat(device, &sbmnt)) {
	newtWinMessage("Error", "OK",
		       "Can't stat %s: %s", device, strerror(errno));
	goto  err_ret0;
    }
    if (S_ISBLK(sbmnt.st_mode)) {
	if (mount(device, "/tmp/mnt1", fstype, MS_RDONLY, NULL)) {
	    newtWinMessage("Error", "OK", "Mount the floppy on %s as %s failed: %s",
			   device, fstype, strerror(errno));
	    goto  err_ret0;
	}
    } else {
	if (lomount(device, "/tmp/mnt1", fstype)) {
	    newtWinMessage("Error", "OK", "Lomount %s as %s failed: %s",
			   device, fstype, strerror(errno));
	    goto  err_ret0;
	}
    }

    namefd = open("/tmp/mnt1/name", O_RDONLY);
    if (namefd < 0) {
	newtWinMessage("Error", "OK",
		       "Failed in get the name of the driver floppy: %s",
		       strerror(errno));
	goto  err_ret1;
    }
    rl = read(namefd, namebuf, sizeof(namebuf) - 1);
    if (rl < 0) {
	newtWinMessage("Error", "OK",
		       "Failed in get the name of the driver floppy: %s",
		       strerror(errno));
	goto  err_ret2;
    }
    if (rl > 0 && namebuf[rl - 1] == '\n')  --rl;
    namebuf[rl] = 0;

    snprintf(chknamebuf, sizeof(chknamebuf), "/tmp/drvfd.names/%s", namebuf);
    if (!stat(chknamebuf, &sbuf)) {
	/* The driver floppy has been loaded. */
	newtWinMessage("Prompt", "OK",
		       "The driver floppy %s has been loaded.", namebuf);
	goto  err_ret2;
    }

    if (stat("/tmp/mnt1/drivers.cpio.gz", &sbuf)) {
	newtWinMessage("Error", "OK",
		       "Failed in reading the drivers in the floppy %s: %s",
		       namebuf, strerror(errno));
	goto  err_ret2;
    }
    cpiofd = open("/tmp/mnt1/drivers.cpio.gz", O_RDONLY);
    if (cpiofd < 0) {
	newtWinMessage("Error", "OK",
		       "Failed in reading the drivers in the floppy %s: %s",
		       namebuf, strerror(errno));
	goto  err_ret2;
    }

    pipe(p);
    if ((pid = fork()) < 0) {
	newtWinMessage("Error", "OK", "fork failed: %s", strerror(errno));
	goto  err_ret3;
    } else if (pid == 0) {
	/* Child process. */
	close(cpiofd);  close(namefd);
	close(p[1]);
	dup2(p[0], 0);
	pipe(p);
	if ((pid = fork()) < 0) {
	    mblog("gzip process fork failed: %s\n", strerror(errno));
	    exit(1);
	} else if (pid == 0) {
	    close(p[1]);
	    dup2(p[0], 0);
	    execl("/bin/cpio", "/bin/cpio", "-diu", NULL);
	    exit(1);
	}
	dup2(p[1], 1);
	close(p[0]);
	execl("/bin/gunzip", "/bin/gunzip", "-c", "-");
	exit(1);
    }
    close(p[0]);

    newt_copy_file(cpiofd, p[1], sbuf.st_size, "Loading", "drivers.cpio.gz");
    close(p[1]);
    while ((r = waitpid(pid, &status, 0)) != pid) {
	if (r < 0 && errno == EINTR)  continue;
	if (r < 0)
	    mblog("loadfloppy: waitpid report: %s\n", strerror(errno));
	else
	    mblog("loadfloppy: waitpid return: %d\n", r);
    }
#ifdef USE_PCITABLE
    /* Append the pcitable to the /usr/share/hwdata/pcitable. */
    pcitable_rfd = open("/tmp/mnt1/mbdrv.pcitable", O_RDONLY);
    if (pcitable_rfd < 0)
	mblog("open(/tmp/mnt1/mbdrv.pcitable, O_RDONLY) failed: %s\n",
	      strerror(errno));
    else {
	pcitable_wfd = open("/usr/share/hwdata/pcitable",
			    O_WRONLY | O_CREAT | O_APPEND);
	if (pcitable_wfd < 0)
	    mblog("open(/usr/share/hwdata/pcitable, ...) failed: %s\n",
		  strerror(errno));
	else {
	    char    buffer[4096];
	    ssize_t rl, wl;
	    while ((rl = read(pcitable_rfd, buffer, sizeof(buffer))) > 0)
		if ((wl = write(pcitable_wfd, buffer, rl)) != rl) {
		    mblog("Append /usr/share/hwdata/pcitable failed: %s\n",
			  strerror(errno));
		    break;
		}
	    close(pcitable_wfd);
	}
	close(pcitable_rfd);
    }

    /* Merge /usr/share/hwdata/pci.ids */
    merge_pciids();
#endif

    /* Generate the required mark. */
    if ((markfd = open(chknamebuf, O_CREAT | O_WRONLY)) >= 0)
	close(markfd);

  err_ret3:
    close(cpiofd);
  err_ret2:
    close(namefd);
  err_ret1:
    if (S_ISBLK(sbmnt.st_mode))
	umount("/tmp/mnt1");
    else
	loumount("/tmp/mnt1");
  err_ret0:
    return;
}

#ifdef USE_PCITABLE
static void pciids_parse(char *line, char *vendor_id, char *device_id, char *subvendor_id, char *subdevice_id);
static int pciids_cmp(char *vendor_id1, char *device_id1, char *subvendor_id1, char *subdevice_id1,
                      char *vendor_id2, char *device_id2, char *subvendor_id2, char *subdevice_id2);

static void merge_pciids(void)
{
/* Merge /usr/share/hwdata/pci.ids */
    FILE *fp1, *fp2, *fpw;
    char line1[256], line2[256];
    char vendor_id1[5], device_id1[5], subvendor_id1[5], subdevice_id1[5];
    char vendor_id2[5], device_id2[5], subvendor_id2[5], subdevice_id2[5];
    int need_line1, need_line2, need_parse1, need_parse2, cmp;

    fp1 = fopen("/tmp/mnt1/mbdrv.pci.ids", "r");
    //fp1 = fopen("mbdrv.pci.ids", "r");
    if (fp1 == NULL) {
        mblog("open /tmp/mnt1/mbdrv.pci.ids failed: %s\n",
              strerror(errno));
        goto err_open_drvids;
    }
    fp2 = fopen("/usr/share/hwdata/pci.ids", "r");
    //fp2 = fopen("pci.ids", "r");
    if (fp2 == NULL) {
        mblog("open /usr/share/hwdata/pci.ids failed: %s\n",
              strerror(errno));
        goto err_open_oldids;
    }
    fpw = fopen("/usr/share/hwdata/pci.ids.new", "w");
    //fpw = fopen("pci.ids.new", "w");
    if (fpw == NULL) {
        mblog("create /usr/share/hwdata/pci.ids.new failed: %s\n",
              strerror(errno));
        goto err_open_newids;
    }
    fgets(line1, sizeof(line1), fp1);
    fgets(line2, sizeof(line2), fp2);
    need_line1 = need_line2 = 0;
    need_parse1 = need_parse2 = 1;
    *vendor_id1 = *device_id1 = *subvendor_id1 = *subdevice_id1 = '\0';
    *vendor_id2 = *device_id2 = *subvendor_id2 = *subdevice_id2 = '\0';
    while (!feof(fp1) && !feof(fp2)) {
        if (need_parse1) {
            pciids_parse(line1, vendor_id1, device_id1, subvendor_id1, subdevice_id1);
            need_parse1 = 0;
        }
        if (need_parse2) {
            pciids_parse(line2, vendor_id2, device_id2, subvendor_id2, subdevice_id2);
            need_parse2 = 0;
        }
        cmp = pciids_cmp(vendor_id1, device_id1, subvendor_id1, subdevice_id1,
                         vendor_id2, device_id2, subvendor_id2, subdevice_id2);
        if (cmp < 0) {
            fputs(line1, fpw);
            need_line1 = 1;
        } else if (cmp > 0) {
            fputs(line2, fpw);
            need_line2 = 1;
        } else {
            fputs(line1, fpw);
            need_line1 = need_line2 = 1;
        }
        if (need_line1) {
            fgets(line1, sizeof(line1), fp1);
            need_line1 = 0;
            need_parse1 = 1;
        }
        if (need_line2) {
            fgets(line2, sizeof(line2), fp2);
            need_line2 = 0;
            need_parse2 = 1;
        }
    }
    while (!feof(fp1)) {
        fputs(line1, fpw);
        fgets(line1, sizeof(line1), fp1);
    }
    while (!feof(fp2)) {
        fputs(line2, fpw);
        fgets(line2, sizeof(line2), fp2);
    }

    remove("/usr/share/hwdata/pci.ids");
    if (rename("/usr/share/hwdata/pci.ids.new", "/usr/share/hwdata/pci.ids") < 0) {
        mblog("rename /usr/share/hwdata/pci.ids.new failed: %s\n",
	      strerror(errno));
    }

    fclose(fpw);
  err_open_newids:
    fclose(fp2);
  err_open_oldids:
    fclose(fp1);
  err_open_drvids:
    ;
}

static void pciids_parse(char *line, char *vendor_id, char *device_id, char *subvendor_id, char *subdevice_id)
{
    if (line[0] != '\t') {
        strncpy(vendor_id, line, 4);
        vendor_id[4] = '\0';
        *device_id = *subvendor_id = *subdevice_id = '\0';
    } else if (line[0] == '\t' && line[1] != '\t') {
        strncpy(device_id, line+1, 4);
        device_id[4] = '\0';
        *subvendor_id = *subdevice_id = '\0';
    } else {
        *subvendor_id = *subdevice_id = '\0';
        strncpy(subvendor_id, line+2, 4);
        strncpy(subdevice_id, line+2+4+1, 4);
        subvendor_id[4] = subdevice_id[4] = '\0';
    }
}


static int pciids_cmp(char *vendor_id1, char *device_id1, char *subvendor_id1, char *subdevice_id1,
               char *vendor_id2, char *device_id2, char *subvendor_id2, char *subdevice_id2)
{
    int cmp;
    cmp = strcmp(vendor_id1, vendor_id2);
    if (cmp != 0)
        return cmp;
    cmp = strcmp(device_id1, device_id2);
    if (cmp != 0)
        return cmp;
    cmp = strcmp(subvendor_id1, subvendor_id2);
    if (cmp != 0)
        return cmp;
    cmp = strcmp(subdevice_id1, subdevice_id2);
    return cmp;
}
#endif
